package de.lighti.starcraft.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import de.lighti.starcraft.bwapi.BWAPI;
import de.lighti.starcraft.bwapi.Bwapi3_7_4;
import de.lighti.starcraft.model.Match;
import de.lighti.starcraft.model.StarcraftBot;
import de.lighti.starcraft.model.StarcraftMap;
import de.lighti.starcraft.network.ByteBufferBackedInputStream;

public class GameDirHandler {

    private static final BWAPI bwApiWriter;
    private static String starcraftBaseBath;
    private static Map<String, ByteBuffer> BOOTSTRAP_FILES;

    static {
        try {
            bwApiWriter = new Bwapi3_7_4();
            BOOTSTRAP_FILES = new HashMap<String, ByteBuffer>();

            BOOTSTRAP_FILES.put( "SNP_DirectIP.snp",
                            ByteBufferBackedInputStream.readStreamInputBuffer( GameDirHandler.class.getResourceAsStream( "SNP_DirectIP.snp" ) ) );
            BOOTSTRAP_FILES.put( "characters\\default.spc",
                            ByteBufferBackedInputStream.readStreamInputBuffer( GameDirHandler.class.getResourceAsStream( "default.spc" ) ) );
            BOOTSTRAP_FILES.put( "characters\\default.mpc",
                            ByteBufferBackedInputStream.readStreamInputBuffer( GameDirHandler.class.getResourceAsStream( "default.mpc" ) ) );

            BOOTSTRAP_FILES.put( "gmp-vc90-mt.dll",
                            ByteBufferBackedInputStream.readStreamInputBuffer( GameDirHandler.class.getResourceAsStream( "gmp-vc90-mt.dll" ) ) );
            BOOTSTRAP_FILES.put( "gmp-vc90-mt-gd.dll",
                            ByteBufferBackedInputStream.readStreamInputBuffer( GameDirHandler.class.getResourceAsStream( "gmp-vc90-mt-gd.dll" ) ) );
            BOOTSTRAP_FILES.put( "mpfr-vc90-mt.dll",
                            ByteBufferBackedInputStream.readStreamInputBuffer( GameDirHandler.class.getResourceAsStream( "mpfr-vc90-mt.dll" ) ) );
            BOOTSTRAP_FILES.put( "mpfr-vc90-mt-gd.dll",
                            ByteBufferBackedInputStream.readStreamInputBuffer( GameDirHandler.class.getResourceAsStream( "mpfr-vc90-mt-gd.dll" ) ) );

            BOOTSTRAP_FILES.put( "bwapi-data\\TournamentModule.dll",
                            ByteBufferBackedInputStream.readStreamInputBuffer( GameDirHandler.class.getResourceAsStream( "TournamentModule.dll" ) ) );
        }
        catch (final IOException e) {
            throw new RuntimeException( e );
        }
    }

    private final static Logger LOGGER = Logger.getLogger( GameDirHandler.class.getName() );

    private static void bootstrapGamedir() throws IOException {
        final File bwApiBasePath = new File( starcraftBaseBath + "\\bwapi-data\\" );
        if (!bwApiBasePath.exists()) {
            LOGGER.info( "Creating " + bwApiBasePath.getAbsolutePath() );
            bwApiBasePath.mkdirs();
        }
        final File replayPath = new File( starcraftBaseBath + "\\maps\\replays\\bwapi\\" );
        if (!replayPath.exists()) {
            LOGGER.info( "Creating " + replayPath.getAbsolutePath() );
            replayPath.mkdirs();
        }
        final File writePath = new File( starcraftBaseBath + "\\bwapi-data\\write\\" );
        if (!writePath.exists()) {
            LOGGER.info( "Creating " + writePath.getAbsolutePath() );
            writePath.mkdirs();
        }
        final File readPath = new File( starcraftBaseBath + "\\bwapi-data\\read\\" );
        if (!readPath.exists()) {
            LOGGER.info( "Creating " + readPath.getAbsolutePath() );
            readPath.mkdirs();
        }
        final File charactersPath = new File( starcraftBaseBath + "\\characters\\" );
        if (!charactersPath.exists()) {
            LOGGER.info( "Creating " + charactersPath.getAbsolutePath() );
            charactersPath.mkdirs();
        }

        for (final Map.Entry<String, ByteBuffer> e : BOOTSTRAP_FILES.entrySet()) {
            final File f = new File( starcraftBaseBath + "\\" + e.getKey() );
            if (f.exists()) {
                f.delete();
            }
            f.createNewFile();
            FileOutputStream w = null;
            try {
                w = new FileOutputStream( f );
                e.getValue().rewind();
                w.getChannel().write( e.getValue() );
                LOGGER.info( "Writing " + e.getValue().position() + " bytes to " + f.getAbsolutePath() );
            }
            finally {
                if (w != null) {
                    w.close();
                }
            }
        }
    }

    public static void cleanupGamedir() {
        final File bwApiBasePath = new File( starcraftBaseBath + "\\bwapi-data\\" );
        if (bwApiBasePath.exists()) {
            LOGGER.info( "Removing " + bwApiBasePath.getAbsolutePath() );
            deleteDirectory( bwApiBasePath );
        }
        for (final String path : BOOTSTRAP_FILES.keySet()) {
            final File f = new File( starcraftBaseBath + "\\" + path );

            if (f.exists()) {
                LOGGER.info( "Removing " + f.getAbsolutePath() );
                f.delete();
            }
        }
        final File replayPath = new File( starcraftBaseBath + "\\maps\\replays\\bwapi\\" );
        if (replayPath.exists()) {
            LOGGER.info( "Removing " + replayPath.getAbsolutePath() );
            deleteDirectory( replayPath );
        }
    }

    private static boolean deleteDirectory( File path ) {
        if (path.exists()) {
            final File[] files = path.listFiles();
            for (final File file : files) {
                if (file.isDirectory()) {
                    deleteDirectory( file );
                }
                else {
                    file.delete();
                }
            }
        }
        return path.delete();
    }

    public static void prepareGame( Match match, Boolean isHost ) throws IOException {
        cleanupGamedir();
        bootstrapGamedir();

        final File bwApiBasePath = new File( starcraftBaseBath + "\\bwapi-data\\" );

        if (!bwApiBasePath.exists()) {
            if (!bwApiBasePath.mkdirs()) {
                throw new IOException( "unable to crate bwapi dir: " + bwApiBasePath.getAbsolutePath() );
            }
        }
        final File bwApiIni = new File( starcraftBaseBath + "\\bwapi-data\\bwapi.ini" );

        if (match.getAwayBot().isBenchmarkBot() || match.getHomeBot().isBenchmarkBot()) {
            bwApiWriter.setEnemy_count( "1" );
            bwApiWriter.setEnemy_race( match.getAwayBot().getRace() );
            bwApiWriter.setWait_for_max_players( 2 );
            bwApiWriter.setAuto_menu( "SINGLE_PLAYER" );
        }

        bwApiWriter.setMap( match.getMap().getMapLocation() );
        final StarcraftBot myBot = isHost ? match.getHomeBot() : match.getAwayBot();
        final StarcraftBot otherBot = !isHost ? match.getHomeBot() : match.getAwayBot();
        bwApiWriter.writeIni( bwApiIni, myBot, otherBot, match.getGameID(), isHost );
    }

    public static void prepBot( StarcraftBot bot ) throws IOException {
        if (bot == null) {
            throw new IllegalArgumentException( "bot must not be null" );
        }

        if (bot.getBinaryContent() == null || bot.getBinaryContent().isEmpty()) {
            LOGGER.warning( "Bot " + bot.getName() + " has no files attached. This is most likely wrong." );
        }
        else {

            final String botPath = starcraftBaseBath + "\\bwapi-data\\AI\\";
            final File botDir = new File( botPath );
            botDir.mkdirs();

            for (final Map.Entry<String, ByteBuffer> e : bot.getBinaryContent().entrySet()) {
                final File f = new File( botPath + e.getKey() );
                if (f.exists()) {
                    f.delete();
                }
                f.createNewFile();
                FileOutputStream fo = null;
                try {
                    fo = new FileOutputStream( f );
                    e.getValue().rewind();
                    fo.write( e.getValue().array() );
                    LOGGER.info( "Wrote " + e.getValue().capacity() + " bytes to " + f.getAbsolutePath() );
                }
                finally {
                    if (fo != null) {
                        fo.close();
                    }
                }

            }
        }

    }

    public static void prepMap( StarcraftMap localMap ) throws IOException {
        if (localMap == null) {
            throw new IllegalArgumentException( "map must not be null" );
        }

        if (localMap.getBinaryContent() != null) {

            //maps are given relative to the Starcraft dir, i.e. it's not necessary to prepend maps\
            final File f = new File( starcraftBaseBath + "\\" + localMap.getMapLocation() );
            if (f.exists()) {
                LOGGER.warning( f.getAbsolutePath() + " already exists. Removing." );
                f.delete();
            }

            f.createNewFile();
            FileOutputStream fo = null;
            FileOutputStream bwtafo = null;
            try {
                fo = new FileOutputStream( f );
                localMap.getBinaryContent()[0].rewind();
                fo.write( localMap.getBinaryContent()[0].array() );
                LOGGER.info( "Wrote " + localMap.getBinaryContent()[0].capacity() + " bytes to " + f.getAbsolutePath() );
                if (localMap.getBinaryContent().length > 1) {
                    //The map comes with a BWTA file
                    final File bwtadir = new File( starcraftBaseBath + "\\bwapi-data\\BWTA\\" );
                    bwtadir.mkdirs();
                    final File bwta = new File( starcraftBaseBath + "\\bwapi-data\\BWTA\\" + localMap.getBWTAHash() + ".bwta" );
                    bwtafo = new FileOutputStream( bwta );
                    localMap.getBinaryContent()[1].rewind();
                    bwtafo.write( localMap.getBinaryContent()[1].array() );

                }
            }
            finally {
                if (fo != null) {
                    fo.close();
                }
                if (bwtafo != null) {
                    bwtafo.close();
                }
            }
        }
        else {
            LOGGER.warning( "Map " + localMap.getName() + " has no files attached. This is most likely wrong." );
        }
    }

    public static void setStarcraftBaseBath( String starcraftBaseBath ) {
        GameDirHandler.starcraftBaseBath = starcraftBaseBath;
    }

    private GameDirHandler() {

    }
}
